import WebSocket from "ws"
import fetch from "node-fetch"
import { FormData, File } from "formdata-node"
import { fileFromPathSync } from "formdata-node/file-from-path"

export default class Botw {
    #botInfo
    #botToken
    #botGuilds
    #apiUrl
    #ws
    #isStop

    constructor(config) {
        this.#botToken = `Bot ${config.appId}.${config.token}`
    }

    get botId() {
        return this.#botInfo.id
    }

    get botName() {
        return this.#botInfo?.username || this.botConfig.appId
    }

    get botState() {
        if (!this.#ws) return "未连接."
        let state
        switch (this.#ws.readyState) {
            case 0:
                state = "连接中..."
                break
            case 1:
                state = "已连接."
                break
            case 2:
                state = "正在断开连接..."
                break
            case 3:
                state = "已断开连接."
                break
            default:
                state = "未知."
        }
        return state
    }

    stop() {
        this.#isStop = true
        if (this.#ws?.readyState === 1) this.#ws.close()
    }

    getGuildName(guild_id) {
        for (let g of this.#botGuilds) {
            if (g.id === guild_id) {
                return g.name
            }
        }
        return "QQ频道"
    }

    async getBotGuilds() {
        this.#botGuilds = await this.getGuildList()
    }

    async init(config, hMsg) {
        if (this.#ws && this.#ws.readyState !== 3) return

        if (config.sandbox) {
            this.#apiUrl = "https://sandbox.api.sgroup.qq.com"
        } else {
            this.#apiUrl = "https://api.sgroup.qq.com"
        }

        logger.info(`[QQ频道插件][${this.botName}] 开始获取连接地址...`)
        let rsp = await this.callApi("/gateway")
        if (rsp?.url) {
            logger.info(`[QQ频道插件][${this.botName}] 获取连接地址成功.`)
            this.#isStop = false
            this.getBotGuilds()
        } else {
            logger.warn(`[QQ频道插件][${this.botName}] 获取连接地址失败！`)
            logger.debug(rsp)
            return
        }

        let heartbeatO = 0
        let heartbeatM = 0

        let wss = () => {
            logger.info(`[QQ频道插件][${this.botName}] 开始连接...`)
            this.#ws = new WebSocket(rsp.url)

            let heartbeatX = 0
            let heartbeatS = 0

            let heartbeatU = () => {
                this.#ws.send(JSON.stringify({ op: 1, d: null }))
                logger.debug(`[QQ频道插件][${this.botName}] 心跳.首次`)
            }
            let heartbeatV = () => {
                this.#ws.send(JSON.stringify({ op: 1, d: heartbeatS }))
                logger.debug(`[QQ频道插件][${this.botName}] 心跳.`, heartbeatS)
            }

            this.#ws.on("error", (error) => {
                logger.error(`[QQ频道插件][${this.botName}] 连接错误.`)
                logger.debug(error)
            })

            this.#ws.on("close", (code, reason) => {
                logger.info(`[QQ频道插件][${this.botName}] 断开连接.`)
                logger.debug(`[QQ频道插件][${this.botName}]`, this.botState, code, reason.toString())

                if (heartbeatO) {
                    heartbeatO = clearInterval(heartbeatO)
                    logger.info(`[QQ频道插件][${this.botName}] 停止心跳.`)
                }

                if (this.#isStop) return

                if (!heartbeatM) {
                    heartbeatM = setInterval(wss, 3000)
                    logger.info(`[QQ频道插件][${this.botName}] 开始重连.`)
                }
            })

            this.#ws.on("open", () => {
                logger.info(`[QQ频道插件][${this.botName}] 连接成功.`)
                if (heartbeatM) {
                    heartbeatM = clearInterval(heartbeatM)
                    logger.info(`[QQ频道插件][${this.botName}] 停止重连.`)
                }
            })

            this.#ws.on("message", (data, isBinary) => {
                let msg = JSON.parse(data)

                if (msg.s) heartbeatS = msg.s

                switch (msg.op) {
                    case 0:
                        if (msg.t === "READY") {
                            logger.info(`[QQ频道插件][${this.botName}] 鉴权成功.`)
                            this.#botInfo = msg.d.user
                            heartbeatU()
                        } else {
                            hMsg(msg)
                        }
                        return
                    case 9:
                        logger.warn(`[QQ频道插件][${this.botName}] 鉴权失败！`)
                        return
                    case 10:
                        heartbeatX = msg.d.heartbeat_interval
                        logger.info(`[QQ频道插件][${this.botName}] 开始鉴权.`)
                        this.#ws.send(JSON.stringify({
                            op: 2,
                            d: {
                                token: this.#botToken,
                                intents: config.intents
                            }
                        }))
                        return
                    case 11:
                        logger.debug(`[QQ频道插件][${this.botName}] 心跳反应.`, msg)
                        if (!heartbeatO) {
                            heartbeatO = setInterval(heartbeatV, heartbeatX)
                            logger.info(`[QQ频道插件][${this.botName}] 开始心跳.`)
                        }
                        return
                    default:
                        logger.debug(`[QQ频道插件][${this.botName}] 收到消息(未处理).`, msg)
                }
            })
        }

        wss()
    }

    async callApi(url, option = {}) {
        if (!option.headers) option.headers = {}
        option.headers.Authorization = this.#botToken
        return await fetch(this.#apiUrl + url, option).then(r => r.json()).then(r => r).catch(e => e)
    }

    async postMsg(toId, msg, isDms) {
        let body = new FormData()
        if (msg.content) body.append("content", msg.content)
        if (msg.embed) body.append("embed", JSON.stringify(msg.embed))
        if (msg.ark) body.append("ark", JSON.stringify(msg.ark))
        if (msg.message_reference) body.append("message_reference", JSON.stringify(msg.message_reference))
        if (msg.image) body.append("image", msg.image)
        if (msg.msg_id) body.append("msg_id", msg.msg_id)
        if (msg.event_id) body.append("event_id", msg.event_id)
        if (msg.markdown) body.append("markdown", JSON.stringify(msg.markdown))
        if (msg.file_image) body.append("file_image", fileFromPathSync(msg.file_image))
        if (msg.file) body.append("file_image", new File([msg.file], "image"))
        return await this.callApi(isDms ? `/dms/${toId}/messages` : `/channels/${toId}/messages`, { method: "POST", body})
    }

    async createDms(source_guild_id, recipient_id) {
        return await this.callApi("/users/@me/dms", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source_guild_id, recipient_id }) })
    }

    async getGuildList() {
        let retList = []
        while (retList) {
            let url = "/users/@me/guilds?limit=100"
            if (retList.length > 0) {
                url += "&after=" + retList.at(-1).id
            }
            let list = await this.callApi(url)
            if (Array.isArray(list) && list.length > 0) {
                retList = retList.concat(list)
            } else {
                break
            }
        }
        return retList
    }

    async delMessage(to_id, message_id, isDms) {
        return await this.callApi(
            isDms ? `/dms/${to_id}/messages/${message_id}?hidetip=true` : `/channels/${to_id}/messages/${message_id}?hidetip=true`,
            {
                method: "DELETE"
            }
        )
    }
}
